package com.n9.qq.client;

import com.n9.qq.android.AqContext;
import com.n9.qq.android.Status;
import com.n9.qq.exception.ConnectionException;
import com.n9.qq.util.ThreadUtils;
import org.apache.mina.core.filterchain.DefaultIoFilterChainBuilder;
import org.apache.mina.core.future.ConnectFuture;
import org.apache.mina.core.service.IoHandler;
import org.apache.mina.core.session.IdleStatus;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolCodecFilter;
import org.apache.mina.filter.executor.ExecutorFilter;
import org.apache.mina.transport.socket.SocketSessionConfig;
import org.apache.mina.transport.socket.nio.NioSocketConnector;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.net.InetSocketAddress;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author ice
 * @since 2019/1/31 15:40
 */
public abstract class AbstractClient implements MockClient, IoHandler {
    private static final Logger log = LoggerFactory.getLogger(AbstractClient.class);
    /**
     * 连接超时15秒
     */
    private static final long CONN_TIMEOUT = 15 * 1000;
    /**
     * 发送消息缓存
     */
    private Map<Integer, ToServiceMsg> toMsgCache = new ConcurrentHashMap<>();

    private NioSocketConnector connector = new NioSocketConnector();
    private IoSession session;
    protected AqContext context = new AqContext();

    /**
     * @param hostname  主机名
     * @param port      端口
     * @param protocol  协议解析类
     */
    public AbstractClient(String hostname, int port, AbstractProtocol protocol) {
        connector.setHandler(this);

        DefaultIoFilterChainBuilder filterChain = connector.getFilterChain();
        ProtocolCodecFilter filter = new ProtocolCodecFilter(protocol, protocol);
        filterChain.addLast("protocol", filter);
        filterChain.addLast("exceutor", new ExecutorFilter());

        SocketSessionConfig sessionConfig = connector.getSessionConfig();
        sessionConfig.setReadBufferSize(4096);
        sessionConfig.setMaxReadBufferSize(65535);
        sessionConfig.setSendBufferSize(4096);
        sessionConfig.setReceiveBufferSize(4096);

        ConnectFuture connect = connector.connect(new InetSocketAddress(hostname, port));
        boolean connSuccess = connect.awaitUninterruptibly(CONN_TIMEOUT);
        if (connSuccess) {
            session = connect.getSession();

            session.setAttribute(AqContext.class, context);
        } else {
            String errorMsg = "无法连接服务器";
            log.error(errorMsg);
            throw new ConnectionException(errorMsg);
        }

    }

    @Override
    public void send(ToServiceMsg toServiceMsg) {
        send(toServiceMsg, 0);
    }

    @Override
    public <T extends FromServiceMsg> T  send(ToServiceMsg toServiceMsg, long timeout) {
        FromServiceMsg ret = null;
        if (session != null) {
            session.write(toServiceMsg);
            if (timeout > 0) {
                final Integer msgId = toServiceMsg.getId();
                toMsgCache.put(msgId, toServiceMsg);
                try {
                    ThreadUtils.wait(toServiceMsg, timeout);

                    ret = toServiceMsg.getFromServiceMsg();
                } finally {
                    toMsgCache.remove(msgId);
                }
            }
        }
        return (T)ret;
    }

    @Override
    public void sessionCreated(IoSession ioSession) throws Exception {

    }

    @Override
    public void sessionOpened(IoSession ioSession) throws Exception {

    }

    @Override
    public void sessionClosed(IoSession ioSession) throws Exception {

    }

    @Override
    public void sessionIdle(IoSession ioSession, IdleStatus idleStatus) throws Exception {

    }

    @Override
    public void exceptionCaught(IoSession ioSession, Throwable throwable) throws Exception {
        throwable.printStackTrace();
        log.error(throwable.getMessage());
        context.setStatus(Status.OFFLINE);
    }

    /**
     * 更新最后接收消息时间
     */
    private void updateLastRecvMsgTime() {
        this.getContext().setLastRecvMsgTime(System.currentTimeMillis());
    }

    @Override
    public void messageReceived(IoSession ioSession, Object o) throws Exception {
        if (o instanceof FromServiceMsg) {
            updateLastRecvMsgTime();

            FromServiceMsg fromServiceMsg = (FromServiceMsg) o;

            Integer msgId = fromServiceMsg.getId();
            ToServiceMsg toServiceMsg = toMsgCache.get(msgId);
            if (Objects.nonNull(toServiceMsg)) {
                toServiceMsg.setFromServiceMsg(fromServiceMsg);
                ThreadUtils.notifyAll(toServiceMsg);
            } else {
                handle(fromServiceMsg);
            }
        } else {
            log.info("receive other msg:{}", o.toString());
        }
    }

    @Override
    public void messageSent(IoSession ioSession, Object o) throws Exception {

    }

    @Override
    public void inputClosed(IoSession ioSession) throws Exception {

    }

    @Override
    public void close() {
        if (Objects.nonNull(session)) {
            session.closeNow();
        }
    }

    public AqContext getContext() {
        return this.context;
    }


}
